package mobilefeel;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.SocketException;

import mobilefeel.accelerometer.Accelerometer;
import mobilefeel.accelerometer.AccelerometerDispatcher;
import mobilefeel.dto.AccelerometerEvent;
import mobilefeel.dto.CustomData;
import mobilefeel.dto.DataTransferObject;
import mobilefeel.dto.DeviceConnect;
import mobilefeel.dto.TouchEvent;
import mobilefeel.dto.ViewEvent;
import mobilefeel.touchscreen.TouchScreen;
import mobilefeel.touchscreen.TouchScreenDispatcher;

/**
 * An AndroidDevice represents a connected Android device. An AndroidDevice
 * allows the developer to identify a specific device, obtain device
 * information, send {@link Serializable} objects to the device or disconnect
 * it.
 * */
public final class AndroidDevice {

	private MobileFeel mobileFeel;
	private ObjectInputStream input;
	private ObjectOutputStream output;
	private Socket socket;
	private Accelerometer accelerometer;
	private TouchScreen touchScreen;
	private String deviceId;

	private String brand;
	private String device;
	private String display;
	private String manufacturer;
	private String model;
	private String product;

	AndroidDevice(MobileFeel mobileFeel, Socket s) {
		this.mobileFeel = mobileFeel;
		this.socket = s;
		this.accelerometer = Accelerometer.getInstance(this);
		this.touchScreen = TouchScreen.getInstance(this);
		
		this.deviceId = "";
		this.brand = "N/A";
		this.device= "N/A";
		this.display = "N/A";
		this.manufacturer = "N/A";
		this.model= "N/A";
		this.product = "N/A";
		
		Thread receiveThread = new Thread() {
			public void run() {
				try {
					if (socket.isConnected() && !socket.isClosed()) {
						output = new ObjectOutputStream(socket.getOutputStream());
						output.flush();
						input = new ObjectInputStream(socket.getInputStream());		

						receive();
					}
				} catch (IOException e) {}
			}
		};
		receiveThread.start();
	}
	
	private void receive() throws IOException {
		try {
			if (socket.isConnected() && !socket.isClosed()) {
				DataTransferObject dto = (DataTransferObject)this.input.readObject();
				while (true) {
					this.processObject(dto);
					if (socket.isConnected() && !socket.isClosed())
						dto = (DataTransferObject)this.input.readObject();
					else
						break;
				}
			}
		} catch (EOFException e){
			//EOF
			this.close();
		} catch (SocketException e) {
			//Connection reset
			this.close();
		} catch (IOException e) {
			this.close();
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	
	private void processObject(DataTransferObject dto) throws IOException {
		switch (dto.getType()) {
			case DataTransferObject.DEVICE_CONNECT:
				DeviceConnect deviceConnect = (DeviceConnect) dto;
				this.deviceId = deviceConnect.getDeviceId();
				this.brand = deviceConnect.getBrand();
				this.device= deviceConnect.getDevice();
				this.display = deviceConnect.getDisplay();
				this.manufacturer = deviceConnect.getManufacturer();
				this.model= deviceConnect.getModel();
				this.product = deviceConnect.getProduct();
				this.mobileFeel.deviceConnected(this);
				break;
			
			case DataTransferObject.DEVICE_DISCONNECT:
				this.close();
				break;
				
			case DataTransferObject.DEVICE_CUSTOM_DATA:
				CustomData customData = (CustomData) dto;
				this.mobileFeel.receiveCustomData(this, customData.getText(), customData.getObject());
				break;
				
			case DataTransferObject.ACCELEROMETER_EVENT:
				AccelerometerEvent e = (AccelerometerEvent)dto;
				AccelerometerDispatcher.notifyAccelerometerListeners(accelerometer, e);
				break;
					
			case DataTransferObject.TOUCHSCREEN_REGISTER_VIEW:
				ViewEvent viewEventRegister = (ViewEvent) dto;
				TouchScreenDispatcher.registerView(this.touchScreen, viewEventRegister.getId(), viewEventRegister.getName(), viewEventRegister.getWidth(), viewEventRegister.getHeight());
				break;
			
			case DataTransferObject.TOUCHSCREEN_CLICK_EVENT:
				ViewEvent viewEventClick = (ViewEvent) dto;
				TouchScreenDispatcher.clickEvent(this.touchScreen, viewEventClick.getId());
				break;
		
			case DataTransferObject.TOUCHSCREEN_LONGCLICK_EVENT:
				ViewEvent viewEventLongClick = (ViewEvent) dto;
				TouchScreenDispatcher.longClickEvent(this.touchScreen, viewEventLongClick.getId());
				break;
		
			case DataTransferObject.TOUCHSCREEN_TOUCH_EVENT:
				TouchEvent touchEvent = (TouchEvent) dto;
				TouchScreenDispatcher.touchEvent(this.touchScreen, touchEvent);
				break;

		}
	}

	synchronized void sendObject(DataTransferObject dto) throws NotConnectedException, IOException {
		if (socket != null && output != null && socket.isConnected() && !socket.isClosed())
			this.output.writeObject(dto);
		else
			throw new NotConnectedException("Device " + toString() + " is not connected.");
	}
	
	/**
	 * Ends the connection with the Android device. No further communication
	 * with this device will be possible unless it reconnects.
	 * 
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if an error occurs while closing the connection
	 */
	public void disconnect() throws NotConnectedException, IOException {
		DataTransferObject dto = new DataTransferObject();
		dto.setType(DataTransferObject.DEVICE_DISCONNECT);
		sendObject(dto);
	}
	
	/**
	 * Sends a Serializable object to the Android device. On the device's side,
	 * an {@link MobileFeelActivity#onCustomDataReceived(String, Serializable)}
	 * event will be fired.
	 * 
	 * @param text a String defining the type of information being sent
	 * @param object the Serilizable object
	 * @throws NotConnectedException if the device is not connected
	 * @throws IOException if an error occurs while sending data
	 */
	public void sendCustomData(String text, Serializable object) throws NotConnectedException, IOException {
		CustomData customData = new CustomData();
		customData.setType(DataTransferObject.DEVICE_CUSTOM_DATA);
		customData.setText(text);
		customData.setObject(object);
		
		sendObject(customData);
	}
	
	private void close() throws IOException {
		this.input.close();
		this.output.flush();
		this.output.close();
		this.socket.close();
		
		this.mobileFeel.deviceDisconnected(this);
	}

	/**
	 * Gets the {@link Accelerometer} object associated with this Android device.
	 * {@link Accelerometer} will allow developers to capture accelerometer
	 * events from this Android device.
	 * 
	 * @return an {@link Accelerometer} instance
	 * @see Accelerometer#getInstance(AndroidDevice)
	 */
	public Accelerometer getAccelerometer() {
		return accelerometer;
	}

	/**
	 * Gets the {@link TouchScreen} object associated with this Android device.
	 * {@link TouchScreen} will allow developers to capture touchscreen events
	 * from this Android device.
	 * 
	 * @return a {@link TouchScreen} instance
	 * @see TouchScreen#getInstance(AndroidDevice)
	 */
	public TouchScreen getTouchScreen() {
		return touchScreen;
	}

	/**
	 * Gets the hexadecimal device ID.
	 * 
	 * @return a String representation of the hexadecimal device ID
	 */
	public String getDeviceId() {
		return deviceId;
	}

	/**
	 * Gets the Android device's brand.
	 * 
	 * @return a String containing the device's brand
	 */
	public String getBrand() {
		return brand;
	}

	/**
	 * Gets the Android device's name.
	 * 
	 * @return a String containing the device's name
	 */
	public String getDevice() {
		return device;
	}

	/**
	 * Gets the Android device's display information.
	 * 
	 * @return a String containing the device's display information
	 */
	public String getDisplay() {
		return display;
	}

	/**
	 * Gets the Android device's manufacturer.
	 * 
	 * @return a String containing the device's manufacturer
	 */
	public String getManufacturer() {
		return manufacturer;
	}

	/**
	 * Gets the Android device's model name.
	 * 
	 * @return a String containing the device's model
	 */
	public String getModel() {
		return model;
	}

	/**
	 * Gets the Android device's product name.
	 * 
	 * @return a String containing the device's product
	 */
	public String getProduct() {
		return product;
	}

	/** 
	 * Returns a string containing a concise, human-readable description of
	 * this object. It is equivalent to the following expression: <br/>
	 * getManufacturer() + " " + getModel() + " (" + getDeviceId() + ")"
	 * 
	 * @return a printable representation of this object
	 */
	@Override
	public String toString() {
		return getManufacturer() + " " + getModel() + " (" + getDeviceId() + ")";
	}
	
}
